﻿using System;
using System.Collections.Specialized;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

using Microsoft.Ajax.Utilities;

namespace Fuse8.ContentManagementFramework.Web.FileMinifier
{
	public class FileBundleHandler : IHttpHandler
	{
		public const string SpecialIgnore = "special";

		private static readonly Regex _version = new Regex("(_[0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static readonly StringDictionary _allowedFileTypes = new StringDictionary()
		{
			{".js", "text/javascript"},
			{".css", "text/css"}
		};

		#region IHttpHandler Members

		public bool IsReusable
		{
			get
			{
				return false;
			}
		}

		public void ProcessRequest(HttpContext context)
		{
			string extension = Path.GetExtension(context.Request.PhysicalPath).ToLowerInvariant();

			if (!_allowedFileTypes.ContainsKey(extension))
			{
				throw new HttpException(403, "File type not supported");
			}

			string fileName = _version.Replace(context.Request.PhysicalPath, string.Empty);
			var files = FileBundle.FindFiles(fileName);

			string result = context.IsDebuggingEnabled || (!context.IsDebuggingEnabled && !fileName.Contains(SpecialIgnore)) ?
				ReadContent(files) :
				Minify(ReadContent(files), extension);

			context.Response.Write(result);
			context.Response.ContentType = _allowedFileTypes[extension];

			if (!context.IsDebuggingEnabled)
			{
				SetHeaders(context, files);
			}
		}

		#endregion

		#region Private methods

		private static void SetHeaders(HttpContext context, FileInfo[] files)
		{
			DateTime lastModified = files.Max(f => f.LastWriteTime);

			context.Response.AddFileDependencies(files.Select(f => f.FullName).ToArray());

			context.Response.Cache.SetLastModified(lastModified);
			context.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
			context.Response.Cache.SetExpires(DateTime.Now.AddDays(365));
			context.Response.Cache.SetOmitVaryStar(true);
			context.Response.Cache.SetValidUntilExpires(true);
		}

		private static string Minify(string content, string extension)
		{
			string result = content;

			if (extension == ".js")
			{
				CodeSettings settings = new CodeSettings();
				settings.MinifyCode = true;
				settings.LocalRenaming = LocalRenaming.CrunchAll;
				settings.RemoveFunctionExpressionNames = true;
				settings.EvalTreatment = EvalTreatment.MakeAllSafe;

				Minifier cruncher = new Minifier();
				result = cruncher.MinifyJavaScript(content, settings);
			}
			else if (extension == ".css")
			{
				Minifier cruncher = new Minifier();
				result = cruncher.MinifyStyleSheet(content);
			}

			return result;
		}

		private static string ReadContent(FileInfo[] files)
		{
			StringBuilder sb = new StringBuilder();

			Array.ForEach(files, p => sb.AppendLine(File.ReadAllText(p.FullName)));

			return sb.ToString();
		}

		#endregion
	}
}
